#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "raylib.h"
#include "pub_lib.h"
#include "FreeImage.h"
#include "image.h"
#include "dpi_algo.h"

const int screenWidth = 1024;
const int screenHeight = 768;

char* hint_str = NULL;
char hint_right[100];
cpu_image effect_data;
static float ggamma = 1.0f;

char left_hint[128] = "image size";
char invert_image_name[] = "Fig0304(a)(breast_digital_Xray).tif";
char gamma_image1_name[] = "Fig0308(a)(fractured_spine).tif";
char gamma_image2_name[] = "Fig0309(a)(washed_out_aerial_image).tif";

char histo_equa_image1[] = "Fig0320(1)(top_left).tif";
char histo_match_image[] = "Fig0323(a)(mars_moon_phobos).tif";
char histo_local_image[] = "Fig0326(a)(embedded_square_noisy_512).tif";
char histo_stats_image[] = "Fig0327(a)(tungsten_original).tif";

char hubble_image_name[] = "Fig0334(a)(hubble-original).tif";

#define MAX_CMD_NUM 7
const char command_str_en[MAX_CMD_NUM][MAX_HINT_STRING_LEN] =
{
	//basic
	"1. negative",
	"2. gamma  < 1",
	"3. gamma  > 1",
	//histogram 
	"4. equalization",
	"5. matching",
	"6. local",
	"7. statistics"
};

const char command_str_cn[MAX_CMD_NUM][MAX_HINT_STRING_LEN] =
{
	"1. 图像反转",
	"2. 伽马校正 gamma < 1",
	"3. 伽马校正 gamma > 1",
	"4. 直方图均衡",
	"5. 直方图匹配",
	"6. 局部直方图",
	"7. 直方图统计"
};

static void histo_matching(char* image_name, int order);
static void histo_local(void);
static void histo_stats(char* image_name, float e, float k0, float k1, float k2);

void show_effect_rchg(void)
{
	show_image(effect_data.rchg, &effect_data,\
		effect_data.rx, effect_data.starty);
}

void show_gamma_image(float gamma)
{
	img_gamma(effect_data.right, effect_data.rchg, effect_data.rchg_data, gamma);
	show_effect_rchg();
}

void init_effect_data(void)
{
	memset(&effect_data, 0, sizeof(cpu_image));
	effect_data.win_width = screenWidth;
	effect_data.win_height = screenHeight;
	effect_data.fb_pixels = effect_data.win_width * effect_data.win_height;
	effect_data.startx = 10;
	effect_data.starty = 30;
	effect_data.left_max = get_max_pixel(screenWidth, 0.3f);
	effect_data.right_max = get_max_pixel(screenWidth, 0.7f);
	effect_data.split_line = effect_data.startx + effect_data.left_max + 5;
	effect_data.lx = effect_data.startx;
	effect_data.rx = effect_data.split_line + 5;
}

void init_fb(void)
{
	effect_data.frame_buf = calloc(effect_data.fb_pixels, sizeof(Color));
}

/*
 * 打开图片，读入图片数据
 * 原始图片信息:  ori
 * 左边显示的小图片: left
 * 右边显示的大图片: right
 * 右边显示的大图片的更改后的数据: rchg
 * 右边显示的大图片，中间计算的结果：rchg_data
 * */
int init_effect(char* path)
{
	const char* ext = GetFileExtension(path);

	FIBITMAP* ori;
	ori = open_img(path);
	if (!ori)
		return -1;
	if (!strcmp(ext, ".tif")) {
		read_img(ori, &effect_data);
		if (effect_data.format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) {
			printf("only support [0, 255] 1 byte TIF image\n");
			close_img(ori);
			return -1;
		}
	}
	effect_data.show_histo = 0;
	effect_data.ori = (void*)ori;
	effect_data.left = (void*)ori;
	effect_data.right = (void*)ori;
	//check if need scale of left image
	int max = effect_data.left_max; //最长或最宽不超过360
	int neww = max, newh = max;
		if ( effect_data.image_width > max || effect_data.image_height > max) {
		cal_scaled_val(max, effect_data.image_width, effect_data.image_height,\
				&neww, &newh);
		effect_data.left = FreeImage_Rescale(ori, neww, newh, FILTER_BILINEAR);
	}

	//update left_hint[]
	if(effect_data.ori != effect_data.left) {
		sprintf(left_hint, "ori:%dx%d  chg:%dx%d",\
			effect_data.image_width, effect_data.image_height,\
			neww, newh);
	} else {
		sprintf(left_hint, "ori:%d x %d",\
			effect_data.image_width, effect_data.image_height);
	}

	//check if need scale of right image
	max = effect_data.right_max;
	neww = effect_data.image_width;
	newh = effect_data.image_height;
	if (effect_data.image_width > max || effect_data.image_height > max) {
		cal_scaled_val(max, effect_data.image_width, effect_data.image_height, \
			&neww, &newh);
		effect_data.right = FreeImage_Rescale(ori, neww, newh, FILTER_BILINEAR);
	}

	effect_data.rchg = FreeImage_Copy(effect_data.right, 0, 0, neww, newh);
	//int data[w][h]
	effect_data.rchg_data = calloc(neww * newh, sizeof(float));

	effect_data.image_size = GetPixelDataSize(effect_data.image_width,\
		effect_data.image_height, effect_data.format);
	effect_data.pix_len = get_pixel_bytes(effect_data.image_width,\
		effect_data.image_height, effect_data.image_size);

	return 0;
}

void unload_image(void)
{
	if(effect_data.left != effect_data.ori)
		close_img(effect_data.left);
	if(effect_data.right != effect_data.ori)
		close_img(effect_data.right);
	if(effect_data.ori)
		close_img(effect_data.ori);
	if(effect_data.rchg)
		close_img(effect_data.rchg);
	if(effect_data.rchg_data)
		free(effect_data.rchg_data);

}
void destroy_effect(void)
{
	free(effect_data.frame_buf);
	unload_image();
}

void switch_next_image(char* filename)
{
	int ret;
	char path[256];

	clear_fb(&effect_data, RAYWHITE);
	unload_image();
	get_full_path(IMAGE_PATH, filename, path);
	ret = init_effect(path);
	if (ret)
		return;
	show_image(effect_data.left, &effect_data,\
		effect_data.startx, effect_data.starty);
}

void handle_key_down(void)
{
	if (effect_data.cmd == 2) {
		if (ggamma == 1.0f) {//first
			ggamma = 0.6f;
		} else {
			ggamma -= 0.1f;
			if (ggamma < 0.3f) {
				ggamma = 0.3f;
				printf("最多校正到 gamma=0.3\n");
				hint_str = "till to end gamma = 0.3";
				return;
			}
		}
		printf("gamma = %f\n", ggamma);
		sprintf(hint_right, "gamma=%f,press down key", ggamma);
		hint_str = hint_right;
		show_gamma_image(ggamma);
	}

}

void handle_key_up(void)
{
	if (effect_data.cmd != 3)
		return;
	ggamma += 0.5f;
	if (ggamma > 5.0f) {
		ggamma = 5.0f;
		printf("最多校正到 %f\n", ggamma);
		hint_str = "till to end gamma=5.0";
		return;
	}
	printf("gamma = %f\n", ggamma);
	sprintf(hint_right, "gamma=%f,press up key", ggamma);
	hint_str = hint_right;
	show_gamma_image(ggamma);
}

void handle_key_space(void)
{
	if (effect_data.cmd == 5) {
		//直方图匹配
		effect_data.order++;
		if (effect_data.order > 3) {
			effect_data.order = 3;
			return;
		}
		histo_matching(histo_match_image, effect_data.order);
	}
}

void histo_equalize(char* image_name)
{
	int max_width = effect_data.right_max;
	int max_new;
	max_new = split_right_window(effect_data.win_width, 0.7f);
	effect_data.right_max = max_new;
	switch_next_image(image_name);

	effect_data.right_max = max_width;
	image_histogram_equa(effect_data.right,\
			effect_data.rchg, effect_data.sk_data);
	//FreeImage_Save(FIF_TIFF, effect_data.right, "changed.tif", 0);
	show_effect_rchg();

	get_histogram(effect_data.right, effect_data.ori_histo);
	get_histogram(effect_data.rchg, effect_data.chg_histo);
	effect_data.show_histo = 4;
}

void draw_right_his(unsigned int* histo)
{
	int x, y, xend, yend, w, h;
	w = FreeImage_GetWidth(effect_data.rchg);
	h = FreeImage_GetHeight(effect_data.rchg);
	x = effect_data.rx + w + 5;
	y = effect_data.starty;
	xend = effect_data.win_width - 5;
	yend = y + h;
	DrawText("changed", x + 20, y + 20, 20, BLACK);
	draw_histogram(effect_data.frame_buf, effect_data.win_width,\
		x, y, xend, yend, histo);
}

void draw_bottom_his(unsigned int* histo)
{
	int x, y, xend, yend, w, h;
	w = FreeImage_GetWidth(effect_data.rchg);
	h = FreeImage_GetHeight(effect_data.rchg);
	x = effect_data.rx;
	y = effect_data.starty + h + 5;
	xend = x + w;
	yend = effect_data.win_height - 5;
	DrawText("Origial", x + 20, y + 20, 20, BLACK);
	draw_histogram(effect_data.frame_buf, effect_data.win_width,\
		x, y, xend, yend, histo);
}

void draw_bottom_right_his_trans(unsigned char *sk)
{
	int x, y, xend, yend, w, h;
	w = FreeImage_GetWidth(effect_data.rchg);
	h = FreeImage_GetHeight(effect_data.rchg);

	x = effect_data.rx + w + 5;
	xend = effect_data.win_width - 5;
	y = effect_data.starty + h + 5;
	yend = effect_data.win_height - 5;

	DrawText("translation", x + 20, y + 20, 20, BLACK);
	draw_his_trans_func(effect_data.frame_buf, effect_data.win_width,\
		x, y, xend, yend, sk);
}

void draw_right_full(unsigned int* histo)
{
	int x, y, xend, yend, w, h;
	w = FreeImage_GetWidth(effect_data.rchg);
	h = FreeImage_GetHeight(effect_data.rchg);
	x = effect_data.rx;
	y = effect_data.starty;
	xend = effect_data.win_width - 5;
	yend = effect_data.win_height - 5;
	DrawText("histogram", x + 20, y + 20, 20, BLACK);
	draw_histogram(effect_data.frame_buf, effect_data.win_width, \
		x, y, xend, yend, histo);
}

void histo_matching(char* image_name, int order)
{
	hint_str = hint_right;
	if(order == 0) {
		//first, 直方图均衡变换
		histo_equalize(image_name);
		sprintf(hint_right, "%s, press key space", command_str_en[4]);
		hint_str = hint_right;
	} else if(order == 1) {
		switch_next_image(image_name);
		//得到右侧大图的sk_data
		image_histogram_equa(effect_data.right,\
			effect_data.rchg, effect_data.sk_data);
		get_histogram(effect_data.right, effect_data.ori_histo);
		get_match_histogram(effect_data.chg_histo);
		//print_histo_data(effect_data.ori_histo);
		//根据期望的直方图，得到对应的的变换函数
		unsigned char gzq_data[256];
		unsigned int total;
		total = histo_get_total(effect_data.chg_histo);
		get_sk_value(effect_data.chg_histo, gzq_data, total);

		//gzq to sk
		histo_get_new_mapping(gzq_data, effect_data.sk_data);
		histo_apply_mapping(effect_data.right,\
			effect_data.rchg, effect_data.sk_data);
		effect_data.show_histo = 0;
		show_effect_rchg();
	}
	else if (order == 2) {
		effect_data.show_histo = 5;
		clear_fb_right(&effect_data, RAYWHITE);
		sprintf(hint_right, "%s, end", command_str_en[4]);
		hint_str = hint_right;
	}
}

/* 局部直方图  
 * 示例图例不适合展示局部直方图 */
void histo_local(void)
{
	switch_next_image(histo_local_image);
	image_histogram_local(effect_data.right, effect_data.rchg, 3);
	show_effect_rchg();
}

/* 直方图统计 */
void histo_stats(char* image_name, float e, float k0, float k1, float k2)
{
	switch_next_image(histo_stats_image);
	get_histogram(effect_data.right, effect_data.ori_histo);

	unsigned char gavg;
	unsigned int gsigma;
	gavg = histo_get_image_avg(effect_data.right, effect_data.ori_histo);
	gsigma = histo_get_image_sigma(effect_data.right, gavg);

	image_histogram_stats(effect_data.right,\
		effect_data.rchg, 3, gavg, gsigma, e, k0, k1, k2);
	show_effect_rchg();
}

void special_effect(int cmd)
{
	//repeate press same key
	if (effect_data.cmd == cmd)
		return;

	if(cmd <= MAX_CMD_NUM) {
		effect_data.cmd = cmd;
		hint_str = (char*)command_str_en[cmd - 1];
		SetWindowTitle(command_str_cn[cmd - 1]);
		printf("%s\n", command_str_cn[cmd - 1]);
	}

	switch (cmd) {
	case 1:
		ggamma = 1.0f;
		switch_next_image(invert_image_name);
		img_negative(effect_data.right, effect_data.rchg);
		show_effect_rchg();
		break;
	case 2:
		if (ggamma < 1.0f)
			return;
		ggamma = 1.0f;
		printf("伽马校正gamma=%f, 按向下方向键\n", ggamma);
		hint_str = "gamma=1.0,press down key";
		switch_next_image(gamma_image1_name);
		show_gamma_image(ggamma);
		break;
	case 3:
		printf("伽马校正gamma>1.0, 按向上方向键\n");
		hint_str = "gamma=1.0, press up key";
		ggamma = 1.0f;
		switch_next_image(gamma_image2_name);
		show_gamma_image(ggamma);
		break;
	case 4: //直方图均衡
		histo_equalize(histo_equa_image1);
		break;
	case 5: //直方图匹配
		effect_data.order = 0;
		histo_matching(histo_match_image, 0);
		break;
	case 6: //局部直方图均衡
		histo_local();
		hint_str = "local equalized";
		break;
	case 7: //直方图统计
		histo_stats(histo_stats_image, 4.0f, 0.4f, 0.02f, 0.4f);
		break;
	case KEY_DOWN:
		handle_key_down();
		break;
	case KEY_UP:
		handle_key_up();
		break;
	case KEY_SPACE:
		handle_key_space();
		break;
	default:
		hint_str = "not support command";
		break;
	}//end switch
}


int main(int argc, char** argv)
{
	// Initialization
	//--------------------------------------------------------------------------------------
	FreeImage_Initialise(TRUE);
	init_effect_data();
	init_fb();

	InitWindow(screenWidth, screenHeight, "灰度变换");
	// NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
	SetTargetFPS(60);               // Set our game to run at 60 frames-per-second
	//--------------------------------------------------------------------------------------

	int key = 0;
	int change = 0;
	Vector2 mouse = GetMousePosition();
	hint_str = "press number key";
	print_info_cn(command_str_cn, MAX_CMD_NUM);

	// Main game loop
	while (!WindowShouldClose())    // Detect window close button or ESC key
	{
		// Update
		key = GetKeyPressed();
		if (key >= '1' && key <= '9') {
			change = 1;
			key = key - '0';
		} else if(key == KEY_DOWN || key == KEY_UP){
			change = 1;
		}
		else if(key == KEY_SPACE) {
			change = 1;
		}
		else if (key == ESC_KEY) {
			break;
		}

		if (change) {
			change = 0;
			special_effect(key);
		}
		//----------------------------------------------------------------------------------
		//----------------------------------------------------------------------------------

		// Draw
		//----------------------------------------------------------------------------------
		BeginDrawing();
		ClearBackground(RAYWHITE);

		DrawText(left_hint, effect_data.startx, 5, 20, BLACK);
		if (hint_str) {
			DrawText(hint_str, effect_data.rx, 0, 20, BLACK);
		}
		show_info_en(command_str_en, MAX_CMD_NUM, effect_data.startx,\
			effect_data.left_max + effect_data.starty + 20);
		draw_image(effect_data.frame_buf, effect_data.win_width, effect_data.win_height);
		DrawLine(effect_data.split_line, effect_data.starty,\
			effect_data.split_line, GetScreenHeight(), GRAY);
		if (effect_data.show_histo == 4) { //直方图均衡
			draw_right_his(effect_data.chg_histo);
			draw_bottom_his(effect_data.ori_histo);
			draw_bottom_right_his_trans(effect_data.sk_data);
		} else if(effect_data.show_histo == 5) { //直方图匹配
			draw_right_full(effect_data.chg_histo);
		}

		mouse = GetMousePosition();
		DrawText(TextFormat("mouse(%d, %d)", (int)mouse.x, (int)mouse.y), \
			GetScreenWidth() - 200, 10, 20, BLACK);

		EndDrawing();
		//----------------------------------------------------------------------------------
	}

	// De-Initialization
	destroy_effect();
	FreeImage_DeInitialise();

	//--------------------------------------------------------------------------------------
	CloseWindow();        // Close window and OpenGL context
	//--------------------------------------------------------------------------------------

	return 0;
}


